﻿/*
 * Copyright 2014 Francois Karman
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using Quartz.Ext;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace Quartz.WebManager.Model
{
    /// <summary>
    /// Provides a validation logic to check the properties of a job instance.
    /// </summary>
    public class PropertyValidator
    {
        /// <summary>
        /// The unique instance of the class.
        /// </summary>
        private static readonly PropertyValidator instance = new PropertyValidator();

        /// <summary>
        /// The known validators.
        /// </summary>
        private readonly IDictionary<string, Func<string, string>> validators;

        /// <summary>
        /// Implements <see cref="PropertyValidator"/> as a singleton.
        /// </summary>
        private PropertyValidator()
        {
            this.validators = new Dictionary<string, Func<string, string>>();
            this.validators.Add(PropertyTypes.Int, o => CheckInt(o));
            this.validators.Add(PropertyTypes.Float, o => CheckFloat(o));
            this.validators.Add(PropertyTypes.DateTime, o => CheckDateTime(o));
            this.validators.Add(PropertyTypes.Url, o => CheckUrl(o));
        }

        /// <summary>
        /// Gets the unique instance of the class.
        /// </summary>
        public static PropertyValidator Instance
        {
            get { return instance; }
        }

        /// <summary>
        /// Adds a new validator.
        /// </summary>
        /// <param name="name">The name of the associated property type.</param>
        /// <param name="validator">The validation function - returns the error message format or null.</param>
        public void AddValidator(string name, Func<string, string> validator)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }
            else if (validator == null)
            {
                throw new ArgumentNullException("validator");
            }

            validators.Add(name, validator);
        }

        /// <summary>
        /// Validates the properties of a specific job instance.
        /// </summary>
        /// <param name="detail">The job instance data.</param>
        /// <returns>
        /// A list of error messages identified by the associated property name.
        /// </returns>
        public IDictionary<string, string> Validate(JobDetail detail)
        {
            if (detail == null)
            {
                throw new ArgumentNullException("detail");
            }
            else if (detail.Definition == null)
            {
                throw new ArgumentException("No job definition for this job", "detail");
            }

            IDictionary<string, string> results = new Dictionary<string, string>();
            foreach (var property in detail.Definition.Properties)
            {
                string value = detail.Properties[property.Name];
                if (string.IsNullOrEmpty(value))
                {
                    if (property.Required)
                    {
                        string message = new RequiredAttribute().FormatErrorMessage(property.DisplayName);
                        results.Add(property.Name, message);
                    }
                }
                else if (this.validators.ContainsKey(property.Type))
                {
                    string format = this.validators[property.Type](value);
                    if (format != null)
                    {
                        string message = string.Format(format, property.DisplayName);
                        results.Add(property.Name, message);
                    }
                }
            }

            return results;
        }

        /// <summary>
        /// Checks that a value can be converted to a DateTime.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>The error format message is the value can't be converted.</returns>
        private static string CheckDateTime(string value)
        {
            DateTime result;
            if (string.IsNullOrEmpty(value) || DateTime.TryParse(value, out result))
            {
                return null;
            }
            else
            {
                return "The field {0} should be a date-time.";
            }
        }

        /// <summary>
        /// Checks that a value can be converted to a float.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>The error format message is the value can't be converted.</returns>
        private static string CheckFloat(string value)
        {
            float result;
            if (string.IsNullOrEmpty(value) || float.TryParse(value, out result))
            {
                return null;
            }
            else
            {
                return "The field {0} should be a float.";
            }
        }

        /// <summary>
        /// Checks that a value can be converted to an int.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>The error format message is the value can't be converted.</returns>
        private static string CheckInt(string value)
        {
            int result;
            if (string.IsNullOrEmpty(value) || int.TryParse(value, out result))
            {
                return null;
            }
            else
            {
                return "The field {0} should be a integer.";
            }
        }

        /// <summary>
        /// Checks that a value can be converted to an url.
        /// </summary>
        /// <param name="value">The value to check.</param>
        /// <returns>The error format message is the value can't be converted.</returns>
        private static string CheckUrl(string value)
        {
            if (string.IsNullOrEmpty(value) || new UrlAttribute().IsValid(value))
            {
                return null;
            }
            else
            {
                return "The field {0} is not a valid url.";
            }
        }
    }
}
